package dove

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"lark/conf"
	"net"
	"strconv"
	"strings"
	"time"
)

var ErrDovekeyNotExist = errors.New("key is not exist")

/*
dove 是为了拿到 dove 中心的配置数据。

因为直接监听 dove 中心的实现比较复杂，需要考虑不同的存储介质和不同的dove 环境，而且对获取值的实时性要求不高
所以采取从本机上的 doveclient 监听的 unix 地址获取最新 dove 值的方案

因为 dove 值的获取不频繁， 而且在以往实践过程中发现 doveclient 经常会有关闭或者崩溃的情况
所以为了简单化，不处理重连和错误状态等逻辑，没有保持到 doveclient 的 unix domain 的长连接，而采用每次新建的方式

当 watch 一个key 但是又没有及时处理更新值的时候， 中间的新值会被丢弃
*/

func Get(key string) ([]byte, error) {
	n := strings.Split(conf.DoveClientAddr, "://")
	if len(n) != 2 {
		return nil, fmt.Errorf("dove client addr error: %s", conf.DoveClientAddr)
	}

	nettype := n[0]
	netaddr := n[1]

	conn, err := net.DialTimeout(nettype, netaddr, conf.DoveClientDialTimeout)
	if err != nil {
		return nil, fmt.Errorf("dial dove client error. %s", err)
	}

	defer conn.Close()

	return getDoveValue(conn, key)
}

func getDoveValue(doveConn net.Conn, doveKey string) ([]byte, error) {
	reqData := make(map[string]interface{})
	reqData["cmd"] = "GetProjectConfig"
	reqData["args"] = map[string]interface{}{"name": doveKey}

	bStr, _ := json.Marshal(reqData)

	headTag := []byte(strconv.Itoa(len(bStr)))
	headTag = append(headTag, make([]byte, 8-len(headTag))...)
	headTag = append(headTag, bStr...)

	doveConn.SetWriteDeadline(time.Now().Add(conf.WriteDoveClientTimeout))

	n, err := doveConn.Write(headTag)
	if err != nil || n != len(headTag) {
		return nil, fmt.Errorf("write dove error. %s, %d, %d", err, n, len(headTag))
	}

	//从doveclient 读取返回
	resHeadTag := make([]byte, 16)

	doveConn.SetReadDeadline(time.Now().Add(conf.ReadDoveClientTimeout))
	n, err = io.ReadFull(doveConn, resHeadTag)
	if err != nil || n != 16 {
		return nil, fmt.Errorf("read dove error. %s, %d, %d", err, n, 16)
	}

	//如果key值存在，会有一个ok的标志位
	if strings.Trim(string(resHeadTag[8:16]), "\000") != "ok" {
		return nil, ErrDovekeyNotExist
	}

	bodyLen, err := strconv.Atoi(strings.Trim(string(resHeadTag[0:7]), "\000"))
	if err != nil {
		return nil, fmt.Errorf("read dove body length error. %s", err)
	}

	rspData := make([]byte, bodyLen)

	doveConn.SetReadDeadline(time.Now().Add(conf.ReadDoveClientTimeout))
	n, err = io.ReadFull(doveConn, rspData)
	if err != nil || n != bodyLen {
		return nil, fmt.Errorf("read dove error. %s, %d, %d", err, n, bodyLen)
	}

	return rspData, nil
}
